package Code.Basic.AI.Pathfinding;

import java.util.HashMap;
import java.util.LinkedList;

import org.jrabbit.base.graphics.transforms.Vector2d;

import Code.Basic.Entity.GenericCharacter;

public class PathMap
{
	private OpenZone[] zones;
	
	public PathMap()
	{
		zones = new OpenZone[0];
	}
	
	public PathMap(OpenZone[] zones)
	{
		this.zones = zones;
	}
	
	public void addOpenZone(OpenZone zone)
	{
		OpenZone[] newZones = new OpenZone[zones.length + 1];
		for(int i = 0; i < zones.length; i++)
		{
			newZones[i] = zones[i];
		}
		newZones[zones.length] = zone;
		zones = newZones;
	}
	
	public boolean removeOpenZone(OpenZone zone)
	{
		int toRemove = -1;
		
		for(int i = 0; i < zones.length && toRemove == -1; i++)
		{
			if(zones[i] == zone)
			{
				toRemove = i;
			}
		}
		
		if(toRemove != -1)
		{
			OpenZone[] newZones = new OpenZone[zones.length - 1];
			
			for(int i = 0; i < toRemove; i++)
			{
				newZones[i] = zones[i];
			}
			for(int i = toRemove + 1; i < zones.length; i++)
			{
				newZones[i - 1] = zones[i];
			}
			
			return true;
		}
		
		return false;
	}
	
	public OpenZone[] getOpenZones()
	{
		return zones;
	}
	
	public OpenZone locateCharacter(GenericCharacter c)
	{
		return getContainingZone(c.location());
	}
	
	public void checkOpenZone(GenericCharacter c)
	{
		if(c.getOpenZone() != null)
		{
			if(!c.getOpenZone().getGeometry().containsPoint(c.location()))
			{
				c.resetZone();
			}
		}
	}
	
	public OpenZone getContainingZone(Vector2d location)
	{
		for(OpenZone zone:zones)
		{
			if(zone.isPointWithin(location))
			{
				return zone;
			}
		}
		return null;
	}
	
	public OpenZone[] findPath(OpenZone start, OpenZone goal)
	{
		OpenZone[] path = null;
		HashMap<OpenZone, Node> allNodes = new HashMap<OpenZone, Node>();
		HashMap<OpenZone, Node> closedNodes = new HashMap<OpenZone, Node>();
		HashMap<OpenZone, Node> openNodes = new HashMap<OpenZone, Node>();
		Node currentNode = new Node(null, start);
		allNodes.put(start, currentNode);
		openNodes.put(currentNode.myZone, currentNode);
		while ((currentNode.myZone != goal) && (openNodes.size() > 0))
		{
			// find the lowest cost open node
			Node[] tempOpen = (Node[]) openNodes.values().toArray();
			for (int i = 0; i < tempOpen.length; i++)
			{
				if (currentNode == null)
				{
					currentNode = tempOpen[i];
				}
				else
				{
					if (tempOpen[i].fValue < currentNode.fValue)
					{
						currentNode = tempOpen[i];
					}
				}
			}
			
			// evaluate surrounding nodes
			ZoneConnection[] connections = (ZoneConnection[]) currentNode.myZone.getConnectedZones();
			for (int i = 0; i < connections.length; i++)
			{
				OpenZone connected = connections[i].getConnectedZone();
				// check if the connected zone has a node
				if (allNodes.containsKey(connected))
				{
					// check if the node is in the open list
					if (openNodes.containsKey(connected))
					{
						// check the gVal
						Node connectNode = allNodes.get(connected);
						if (connectNode.gValue > (currentNode.gValue + connections[i].getGValue()))
						{
							connectNode.parent = currentNode;
							connectNode.gValue = currentNode.gValue + connections[i].getGValue();
							connectNode.fValue = connectNode.gValue + connectNode.hValue;
						}
					}
				}
				else // allNodes does not contain the connected zone's node
				{
					// create a new node and add it to allNodes and openNodes
					Node tempNode = new Node(currentNode, connected);
					allNodes.put(tempNode.myZone, tempNode);
					openNodes.put(tempNode.myZone, tempNode);
					// calculate values for tempNode
					tempNode.gValue = currentNode.gValue + connections[i].getGValue();
					tempNode.hValue = tempNode.myZone.getHValue(goal);
					tempNode.fValue = tempNode.gValue + tempNode.hValue;
					tempNode.parent = currentNode;
				}
			}
			
			// move the current node to the closed list
			openNodes.remove(currentNode);
			closedNodes.put(currentNode.myZone, currentNode);
		}
		
		if (currentNode.myZone == goal)
		{
			// put the zones into an array
			LinkedList<OpenZone> zoneList = new LinkedList<OpenZone>();
			while (currentNode.parent != null)
			{
				zoneList.add(currentNode.myZone);
				currentNode = currentNode.parent;
			}
			zoneList.add(currentNode.myZone);
			
			OpenZone[] tempPath = zoneList.toArray(new OpenZone[0]);
			path = new OpenZone[tempPath.length];
			for (int i = 0; i < path.length; i++)
			{
				path[i] = tempPath[tempPath.length - 1 - i];
			}
		}
		
		return path;
	}
	
	private class Node {
		public Node parent;
		public OpenZone myZone;
		public long hValue;
		public long gValue;
		public long fValue;
		
		public Node(Node parentZone, OpenZone theZone)
		{
			parent = parentZone;
			myZone = theZone;
			hValue = 0;
			gValue = 0;
			hValue = 0;
		}
	}
}